﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Zcold.ZcoldString;

namespace SimulinkParser
{
	/// <summary> SimulinkSystem class
	/// <para> This data structure present a .mdl file </para>
	/// </summary>
	public class SimulinkSystem
	{
		/// <summary> Root parameters
		/// </summary>
		public ParameterList Parameters;

		/// <summary> SimulinkBlocks contained in this system
		/// </summary>
		public List<SimulinkBlock> Blocks;

		/// <summary> Interconnects
		/// </summary>
		public List<SimulinkLine> Lines;

		/// <summary> The name of the SimulinkSystem
		/// <para> This attribute is obtained by searching the SimulinkParameter with name "Name" in the root parameters.  </para>
		/// </summary>
		public string Name { get { return Parameters.LocateParameterByName("Name").Value; } }

		/// <summary> Clear all variables
		/// </summary>
		public void Clear()
		{
			Parameters = new ParameterList();
			Blocks = new List<SimulinkBlock>();
			Lines = new List<SimulinkLine>();
		}

		/// <summary> Construct an empty SimulinkSystem instance
		/// </summary>
		public SimulinkSystem() { Clear(); }

		/// <summary> Construct a SimulinkSystem instance by parsing a .mdl file
		/// </summary>
		/// <param name="FilePath"> .mdl file path </param>
		public SimulinkSystem(string FilePath)
		{
			Clear();
			ParameterBlock FileModel = FileManipulation.ReadFile(FilePath);
			Blocks = FileManipulation.ExtractSimulinkSystemFromFileModel(FileModel.System).Blocks;
			Lines = FileManipulation.ExtractSimulinkSystemFromFileModel(FileModel.System).Lines;
			Parameters = FileManipulation.ExtractSimulinkSystemFromFileModel(FileModel.System).Parameters;
		}

		/// <summary> Check if two SimulinkBlocks are in the same type
		/// </summary>
		/// <param name="A"> One SimulinkBlock </param>
		/// <param name="B"> Another SimulinkBlock </param>
		/// <returns> true: match, false: mismatch </returns>
		private bool BlockTypeMatch(SimulinkBlock A, SimulinkBlock B)
		{
			return A.BlockType.Equals(B.BlockType);
		}

		/// <summary> Check if the specified SimulinkBlock contains a specified parameter
		/// </summary>
		/// <param name="Block"> The SimulinkBlock instance </param>
		/// <param name="Parameter"> The SimulinkParameter instance </param>
		/// <returns> true: Block contains Paramter, false: Block does not contain Paramter </returns>
		private bool BlockContainsParameter(SimulinkBlock Block, SimulinkParameter Parameter)
		{
			bool Result = false;

			foreach (SimulinkParameter P in Block.Parameters.Parameters)
				if (P.Name.Equals(Parameter.Name)) return true;

			return Result;
		}

		/// <summary> Merge default parameters if not presented in the blocks
		/// </summary>
		/// <param name="DefaultBlocks"> Default blocks </param>
		public void MergeDefaultParameters(List<SimulinkBlock> DefaultBlocks)
		{
			for (int i = 0; i < DefaultBlocks.Count; i++)
			{
				for (int j = 0; j < Blocks.Count; j++)
				{
					if (BlockTypeMatch(DefaultBlocks[i], Blocks[j]))
					{
						for (int k = 0; k < DefaultBlocks[i].Parameters.Count; k++)
						{
							// if parameter is not contained in the block, use default value
							if (!BlockContainsParameter(Blocks[j], DefaultBlocks[i].Parameters.Parameters[k]))
								Blocks[j].Parameters.Add(DefaultBlocks[i].Parameters.Parameters[k]);
						}
					}
				}
			}
		}

		/// <summary> Print detailed information to console
		/// </summary>
		/// <param name="Level"> Print indent level <para> Higher level = more indent to the right </para> </param>
		public void PrintInfo(int Level)
		{
			if (Parameters.ContainsParameterByName("Name"))
			{
				for (int i = 0; i < Level; i++)
					Console.Write(" ");

				Console.WriteLine("System Name: " + Name);
			}
			for (int i = 0; i < Level; i++)
				Console.Write(" ");

			Console.WriteLine("Parameters: ...");
			foreach (SimulinkParameter SP in Parameters.Parameters)
			{
				for (int i = -1; i < Level; i++)
					Console.Write(" ");

				Console.WriteLine(SP.Name + " :: " + SP.Value);
			}

			for (int i = 0; i < Level; i++)
				Console.Write(" ");

			if (Blocks.Count <= 0)
				Console.WriteLine("No block.");
			else
			{
				Console.WriteLine("Blocks: ");
				foreach (SimulinkBlock SB in Blocks)
					SB.PrintInfo(Level + 1);
			}

			if (Lines.Count <= 0)
				Console.WriteLine("No line.");
			else
			{
				Console.WriteLine("Lines: ");
				foreach (SimulinkLine SL in Lines)
					SL.PrintInfo(Level + 1);
			}


		}
	}

	/// <summary> SimulinkBlock class
	/// <para> This data structure present a Block in a SimulinkSystem </para>
	/// </summary>
	public class SimulinkBlock
	{
		/// <summary> Internal sub-system
		/// </summary>
		public SimulinkSystem System;

		/// <summary> Root parameters
		/// </summary>
		public ParameterList Parameters;

		/// <summary> Block name attribute
		/// <para> Block name should be unique in a system </para>
		/// </summary>
		public string Name { get { return Parameters.LocateParameterByName("Name").Value; } }

		/// <summary> Block type attribute
		/// <para> Block type can be the same for multiple blocks since it defines the type of the blocks </para>
		/// </summary>
		public string BlockType { get { return Parameters.LocateParameterByName("BlockType").Value; } }

		/// <summary> Block ID attribute
		/// <para> SID should be unique in a system </para>
		/// </summary>
		public string SID { get { return Parameters.LocateParameterByName("SID").Value; } }

		/// <summary> Clear this SimulinkBlock class
		/// </summary>
		public void Clear()
		{
			System = new SimulinkSystem();
			Parameters = new ParameterList();
		}

		/// <summary> Construct an empty SimulinkBlock instance
		/// </summary>
		public SimulinkBlock() { Clear(); }

		/// <summary> Print detailed information to console
		/// </summary>
		/// <param name="Level"> Print indent level <para> Higher level = more indent to the right </para> </param>
		public void PrintInfo(int Level)
		{
			if (Parameters.ContainsParameterByName("Name"))
			{
				for (int i = 0; i < Level; i++)
					Console.Write(" ");
				Console.WriteLine("Block Name: " + Name);
			}

			if (Parameters.ContainsParameterByName("BlockType"))
			{
				for (int i = 0; i < Level; i++)
					Console.Write(" ");
				Console.WriteLine("BlockType: " + BlockType);
			}

			if (Parameters.ContainsParameterByName("SID"))
			{
				for (int i = 0; i < Level; i++)
					Console.Write(" ");
				Console.WriteLine("SID: " + SID);
			}

			for (int i = 0; i < Level; i++)
				Console.Write(" ");
			Console.WriteLine("Block parameters...");
			foreach (SimulinkParameter SP in Parameters.Parameters)
			{
				for (int i = -1; i < Level; i++)
					Console.Write(" ");
				Console.WriteLine(SP.Name + " :: " + SP.Value);
			}
		}
	}

	/// <summary> SimulinkLine class
	/// <para> Interconnect </para>
	/// </summary>
	public class SimulinkLine
	{
		/// <summary> Source end
		/// <para> Each SimulinkLine instance can have only one source end </para>
		/// </summary>
		public End Source;

		/// <summary> Destination end(s)
		/// <para> Each SimulinkLine instance can have multiple destination ends </para>
		/// </summary>
		public List<End> Destination;

		/// <summary> The number of destinations
		/// </summary>
		public int DestinationCount { get { return Destination.Count; } }

		/// <summary> Clear this SimulinkLine instance
		/// </summary>
		public void Clear()
		{
			Source = new End();
			Destination = new List<End>();
		}

		/// <summary> Construct an empty SimulinkLine instance
		/// </summary>
		public SimulinkLine() { Clear(); }

		/// <summary> Construct an one to one line
		/// </summary>
		/// <param name="Source"> Source End </param>
		/// <param name="Destination"> Destination End </param>
		public SimulinkLine(End Source, End Destination)
		{
			this.Source = new End(Source);
			this.Destination = new List<End>() { new End(Destination) };
		}

		/// <summary> Constructe an one to multiple line
		/// </summary>
		/// <param name="Source"> Source End </param>
		/// <param name="Destination"> Destination End list </param>
		public SimulinkLine(End Source, List<End> Destination)
		{
			Clear();
			this.Source = new End(Source);
			this.Destination.AddRange(Destination);
		}

		/// <summary> Print detailed information to console
		/// </summary>
		/// <param name="Level"> Print indent level <para> Higher level = more indent to the right </para> </param>
		public void PrintInfo(int Level)
		{
			for (int i = 0; i < Level; i++)
				Console.Write(" ");

			Console.WriteLine("Source: ");
			Source.PrintInfo(Level + 1);
			for (int i = 0; i < Level; i++)
				Console.Write(" ");
			Console.WriteLine("Destination: ");
			foreach (End e in Destination)
				e.PrintInfo(Level + 1);
		}
	}

	/// <summary> Communication end with name and port
	/// </summary>
	public class End
	{
		/// <summary> Block name
		/// </summary>
		public string Name;

		/// <summary> Port name
		/// </summary>
		public string Port;

		/// <summary> Clear all variables
		/// </summary>
		public void Clear()
		{
			Name = "";
			Port = "";
		}

		/// <summary> Contruct an empty End instance
		/// </summary>
		public End() { Clear(); }

		/// <summary> Construct an End instance with specified Name and Port value
		/// </summary>
		/// <param name="Name"> Block name </param>
		/// <param name="Port"> Port name</param>
		public End(string Name, string Port)
		{
			Clear();
			this.Name = Name;
			this.Port = Port;
		}

		/// <summary> Construct a clone of the given End
		/// </summary>
		/// <param name="Source"> End to be cloned </param>
		public End(End Source)
		{
			Clear();
			this.Name = Source.Name;
			this.Port = Source.Port;
		}

		/// <summary> Print detailed information to console
		/// </summary>
		/// <param name="Level"> Print indent level <para> Higher level = more indent to the right </para> </param>
		public void PrintInfo(int Level)
		{
			for (int i = 0; i < Level; i++)
				Console.Write(" ");
			Console.WriteLine(Name + ", " + Port);
		}
	}
}
